Android架构:第三部分-在Android上应用Clean架构 (译)

到目前为止,在这个系列中,我们已经介绍了一些初学者的错误,并通过了Clean架构。 在最后一部分中,我们将介绍拼图的最后一部分:标签,或者更确切地说:组件。

首先,我将删除我们在Android项目中不使用的东西,然后添加一些我们使用的东西,但在原始的Bob叔叔图中找不到。 它看起来像这样:

clean architecture - 1

我会从最抽象的中心走到边缘。

Entities

Entities,即Domain 对象或业务对象,它们是App的核心。 它们代表了APP的主要功能,您应该能够通过查看它们来了解APP的功能。 它们包含业务逻辑,但仅限于它们 - 验证和类似的东西。 他们不会与外在世界的细节进行互动,也不会处理持久性。 例如,如果您有新闻APP,则这些实体将是类别,文章和商业广告。

Use cases

Use case,又名interactors,又名business services,是Entities的扩展,是业务逻辑的延伸,也就是说。 它们包含的逻辑不仅限于一个实体,而是处理更多的实体。 一个好Use case的一个指标是,你可以用一种通用的语言描述它在一个简单的句子中的作用 - 例如,“从一个账户转移到另一个账户”。你甚至可以用这样的命名来命名该类,例如,TransferMoneyUseCase。

Repositories

Repositories用于坚持Entities。 就这么简单。 它们被定义为接口并用作想要对Entities执行CRUD操作的用例的输出端口。 另外,它们可以公开一些与持久性相关的更复杂的操作,例如过滤,聚合等。 具体的持久性策略,例如数据库或因特网,是在外层实现的。 例如,您可以命名接口AccountRepository。

Presenters

如果你熟悉MVP模式,Presenters就会做你期望他们做的事情。 他们处理用户交互,调用适当的业务逻辑,并将数据发送到UI进行渲染。 这里通常有不同类型的模型之间的映射。 有些人会在这里使用Controller,这很好。 我们使用的Presenter正式被称为监督Controller。 我们通常在每个屏幕上定义一个或两个Presenters,具体取决于屏幕,Presenter的生命周期与视图的生命周期相关联。 一条建议:尝试在Presenter上将您的方法命名为技术不可知论者(technology agnostic)。 假设您不知道视图实现的技术。因此,如果您在视图中具有名为onSubmitOrderButtonClicked和onUserListItemSelected的方法,则处理这些事件的相应演示者方法可以命名为submitOrder和selectUser。

Device

这个组件在通知(再次)例子中已经被取笑了。它包含了诸如传感器,警报,通知,播放器,各种Managers等Android提供东西的实现。它是一个由两部分组成的部分。第一部分是业务逻辑用作与外部世界进行通信的输出端口的内部圈定义的接口。第二部分,这是在图中绘制的,是这些接口的实现。因此,您可以定义例如名为Gyroscope,Alarm,Notifications和Player的接口。注意名称是抽象的和技术不可知(technology agnostic)的。业务逻辑不关心如何显示通知,玩家如何播放声音,或陀螺仪数据来自何处。您可以制作将通知写入终端,将声音数据写入日志或从预定义文件收集陀螺仪数据的实现。这样的实现对于调试或创建确定性环境是非常有用的。但是,您当然必须制作诸如AndroidAlarm,NativePlayer等实现。在大多数情况下,这些实现只是围绕Android的Manager 类的包装。

DB & API

将存储库的实现放在这个组件中。所有这些底层持久化的东西应该在这里:DAO,ORM的东西,Retrofit(或别的东西)的东西,JSON解析等。你也可以在这里实现缓存策略,或者只是使用内存中的持久性,直到完成与应用程序的其余部分。我们最近在团队中进行了一次有趣的讨论。问题是这样的:资源库是否应公开诸如fetchUsersOffline(fetchUsersFromCache)和fetchUsersOnline(fetchUsersFromInternet)之类的方法?换句话说,业务逻辑应该知道数据来自哪里?看过这篇文章的所有内容后,答案很简单:不。但是有一个问题!如果关于数据源的决定是业务逻辑的一部分 - 例如,如果用户可以选择它,或者如果您有明确的离线模式的应用程序,那么您可以添加这样的区别。但是我不会为每个抓取定义两种方法。我可能会在资源库中公开方法,如enterOfflineMode和exitOfflineMode。或者,如果它适用于所有存储库,则可以使用输入和退出方法定义OfflineMode接口,并在业务逻辑端使用它,让存储库查询该模式并在内部进行确定。

UI

将与Android用户界面相关的所有内容放在这里 Activities,fragments,views,adapters等完成。

Modules

下图显示了我们如何将所有这些组件分成Android Studio模块。 你可能会发现另一个更合适的部门。

clean architecture - 2

我们将entities,use cases,repositories和device接口分组到domain模块中。 如果您想获得额外的挑战,并获得永恒的荣耀和完全干净的设计,您可以使该模块成为纯Java模块。 它会阻止你在这里使用快捷方式并将相关的东西放在Android上。

device模块应该包含与Android相关的所有内容,而不是数据持久性和UI。 正如我们已经说过的,data模块应该包含与数据持久性相关的所有内容。 你不能让这两个人进入Java模块,因为他们需要访问各种Android的东西。 你可以将它们变成Android库。

最后,我们将与UI相关的所有内容(包括presenters)分组到UI模块中。 您可以明确地命名为UI,但由于Android中的所有东西,我们将其命名为“app”,就像Android Studio在创建项目时命名的那样。

这样更好些了吗?

为了回答这个问题,我会抛弃Bob叔叔的图表,并将之前描述的组件扩展到像我们用来评估以前类型的体系结构的图表。 这样做后,我们得到这个:

clean architecture - 3

现在让我们应用我们在以前的体系结构中使用的相同条件。

clean architecture - 4

它完全由模块级别,封装级别和类级别分开。 所以SRP应该得到满足。

clean architecture - 5

我们尽可能将Android和现实世界推到了最外面。 业务逻辑不再直接接触Android。

clean architecture - 6

我们有很好的分离类,很容易测试。 接触世界的类可以使用Android测试用例进行测试; 可以使用JUnit测试它。 有人恶意可能会称这类爆炸。 我称之为可测试的。:)

clean architecture - 7

Clean架构:它可能很复杂 - 但它是值得的

我希望我精心挑选的标准并不是为了支持Clean架构而编造的,它会让你尝试一下。 这看起来很复杂,而且这里有很多细节,但它是值得的。 一旦你将所有东西连接起来,测试就容易得多,错误更容易隔离,新特性易于添加,代码更具可读性和可维护性,一切正常,世界满意。

所以,就是这样。 如果您还没有这样做,请查看以前的系列文章:MistakesClean Architecture.。 如果您有任何意见或问题,请在下面留言。 我们总是有兴趣听到你的想法。

阅读我们的Android Architecture系列的第四部分。

您还可以查看其他部分:

Part 5: How to Test Clean Architecture

Part 2: The Clean Architecture

Part 1: every new beginning is hard